﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using CSJ2K.Jpc.Segments;
using CSJ2K.Util.IO;

namespace CSJ2K.Jpc
{
    public class TilePart
    {
        public CodeStream CodeStream;
        public Tile Tile;

        public long Start;
        public int Length;                      // Total length of tile-part header segments

        public SOT Sot;                         // Required at the beginning of each tile-part header
        public COD Cod;                         // Optional; no more than one COD per tile
        public List<COC> Coc = new List<COC>(); // Optional; no more than one COC per component
        public QCD Qcd;                         // Optional; no more than one QCD per tile
        public List<QCC> Qcc = new List<QCC>(); // Optional; no more than one QCC per component
        public List<RGN> Rgn = new List<RGN>(); // Optional; no more than one RGN per component
        public POC Poc;                         // Required if any progression order changes different from main POC
        public PPT Ppt;                         // Optional; either PPM or PPT or codestream packet headers required
        public PLT Plt;                         // Optional
        public List<COM> Com = new List<COM>(); // Optional
        public SOD Sod;                         // Required at the end of each tile-part header

        private long LengthOfHeaders()
        {
            long length = Sot.Length + 2;
            if (Cod != null) length += Cod.Length + 2;
            foreach (COC coc in Coc) length += coc.Length + 2;
            if (Qcd != null) length += Qcd.Length + 2;
            foreach (QCC qcc in Qcc) length += qcc.Length + 2;
            foreach (RGN rgn in Rgn) length += rgn.Length + 2;
            if (Poc != null) length += Poc.Length + 2;
            if (Ppt != null) length += Ppt.Length + 2;
            if (Plt != null) length += Plt.Length + 2;
            foreach (COM com in Com) length += com.Length + 2;
            if (Sod != null) length += Sod.Length + 2;
            return length;
        }

        public List<Packet> Packets = new List<Packet>();

        public TilePart(CodeStream codeStream, Stream source) : this(codeStream, source, 0) { }
        public TilePart(CodeStream codeStream, Stream source, Segment.Markers marker)
        {
            this.CodeStream = codeStream;

            this.Start = source.Position - 2;

            Debug.WriteLine("SOT");
            Debug.Indent();
            Sot = new SOT(source, marker);
            Debug.Unindent();
            Length += Sot.Length + (ushort)2;

            Tile = codeStream.Tiles[Sot.I];

            Segment segment;
            while (Sod == null)
            {
                segment = Segment.FromStream(source, codeStream.Components);
                Length += segment.Length + 2;
                Debug.WriteLine(segment.Marker.ToString());
                Debug.Indent();
                switch (segment.Marker)
                {
                    case Segment.Markers.COD:
                        if (Cod != null) throw (new ApplicationException("Too many COD marker segments in tile part."));
                        Cod = (COD)segment;
                        break;
                    case Segment.Markers.COC: // Multiple
                        Coc.Add((COC)segment);
                        break;
                    case Segment.Markers.QCD:
                        if (Qcd != null) throw (new ApplicationException("Too many QCD marker segments in tile part."));
                        Qcd = (QCD)segment;
                        break;
                    case Segment.Markers.QCC: // Multiple
                        Qcc.Add((QCC)segment);
                        break;
                    case Segment.Markers.RGN: // Multiple
                        Rgn.Add((RGN)segment);
                        break;
                    case Segment.Markers.POC: // Multiple
                        if (Poc != null) throw (new ApplicationException("Too many QCD marker segments in tile part."));
                        Poc = (POC)segment;
                        break;
                    case Segment.Markers.PPT:
                        if (Ppt != null) throw (new ApplicationException("Too many PPT marker segments in tile part."));
                        Ppt = (PPT)segment;
                        break;
                    case Segment.Markers.PLT:
                        if (Plt != null) throw (new ApplicationException("Too many PLT marker segments in tile part."));
                        Plt = (PLT)segment;
                        break;
                    case Segment.Markers.COM:
                        Com.Add((COM)segment);
                        break;
                    case Segment.Markers.SOD:
                        Sod = (SOD)segment;
                        break;
                    default:
                        throw (new ApplicationException("Unexpected segment in tile part header."));
                }
                Debug.Unindent();
            }
            ReadPackets(source);
        }

        // Ugly use of goto to break out of deeply nested for loops just so we can
        // do the proper debug output without duplication.
        public bool ReadPackets(Stream Source)
        {
            bool Completed = false;

            Debug.WriteLine("Reading " + (Sot.P - Length) + " byte BitStream in " + CodeStream.Progressions + " progression.");
            long endPosition = Source.Position + (Sot.P - LengthOfHeaders());
            Debug.Indent();

            BitstreamReader br = new BitstreamReader(Source, endPosition);

            int l, r, c, p;

            try
            {
                switch (CodeStream.Progressions)
                {
                    case COD.ProgressionOrders.LRCP:
                        for (l = Tile.Layer; l < CodeStream.Layers; l++)
                        {
                            for (r = (l == Tile.Layer ? Tile.Resolution : 0); r < CodeStream.Resolutions; r++)
                            {
                                for (c = (l == Tile.Layer && r == Tile.Resolution ? Tile.Component : 0); c < CodeStream.Components; c++)
                                {
                                    for (p = (l == Tile.Layer && r == Tile.Resolution && c == Tile.Component ? Tile.Precinct : 0); p < Tile.Precincts; p++)
                                    {
                                        if (!Tile.PacketCompleted[l, r, c, p]) ReadPacket(br, l, r, c, p);
                                        else Debug.WriteLine("Packet Completed. Layer " + l + " Resolution " + r + " Component " + c + " Precinct " + p);
                                        if (Source.Position >= endPosition) goto EndOfBitstream;
                                    }
                                }
                            }
                        }
                        break;
                    case COD.ProgressionOrders.CPRL:
                        for (c = Tile.Component; c < CodeStream.Components; c++)
                        {
                            for (p = (c == Tile.Component ? Tile.Precinct : 0); p < Tile.Precincts; p++)
                            {
                                for (r = (c == Tile.Component && p == Tile.Precinct ? Tile.Resolution : 0); r < CodeStream.Resolutions; r++)
                                {
                                    for (l = (c == Tile.Component && p == Tile.Precinct && r == Tile.Resolution ? Tile.Layer : 0); l < CodeStream.Layers; l++)
                                    {
                                        if (!Tile.PacketCompleted[l, r, c, p]) ReadPacket(br, l, r, c, p);
                                        if (Source.Position >= endPosition) goto EndOfBitstream;
                                    }
                                }
                            }
                        }
                        break;
                    case COD.ProgressionOrders.PCRL:
                        for (p = Tile.Precinct; p < Tile.Precincts; p++)
                        {
                            for (c = (p == Tile.Precinct ? Tile.Component : 0); c < CodeStream.Components; c++)
                            {
                                for (r = (p == Tile.Precinct && c == Tile.Component ? Tile.Resolution : 0); r < CodeStream.Resolutions; r++)
                                {
                                    for (l = (p == Tile.Precinct && c == Tile.Component && r == Tile.Resolution ? Tile.Layer : 0); l < CodeStream.Layers; l++)
                                    {
                                        if (!Tile.PacketCompleted[l, r, c, p]) ReadPacket(br, l, r, c, p);
                                        if (Source.Position >= endPosition) goto EndOfBitstream;
                                    }
                                }
                            }
                        }
                        break;
                    case COD.ProgressionOrders.RLCP:
                        for (r = Tile.Resolution; r < CodeStream.Resolutions; r++)
                        {
                            for (l = (r == Tile.Resolution ? Tile.Layer : 0); l < CodeStream.Layers; l++)
                            {
                                for (c = (r == Tile.Resolution && l == Tile.Layer ? Tile.Component: 0); c < CodeStream.Components; c++)
                                {
                                    for (p = (r == Tile.Resolution && l == Tile.Layer && c == Tile.Component ? Tile.Precinct : 0); p < Tile.Precincts; p++)
                                    {
                                        if (!Tile.PacketCompleted[l, r, c, p]) ReadPacket(br, l, r, c, p);
                                        if (Source.Position >= endPosition) goto EndOfBitstream;
                                    }
                                }
                            }
                        }
                        break;
                    case COD.ProgressionOrders.RPCL:
                        for (r = Tile.Resolution; r < CodeStream.Resolutions; r++)
                        {                         
                            for (p = (r == Tile.Resolution ? Tile.Precinct : 0); p < Tile.Precincts; p++)
                            {
                                for (c = (r == Tile.Resolution && p == Tile.Precinct ? Tile.Component : 0); c < CodeStream.Components; c++)
                                {
                                    for (l = (r == Tile.Resolution && p == Tile.Precinct && c == Tile.Component ? Tile.Layer : 0); l < CodeStream.Layers; l++)
                                    {
                                        if (!Tile.PacketCompleted[l, r, c, p]) ReadPacket(br, l, r, c, p);
                                        if (Source.Position >= endPosition) goto EndOfBitstream;
                                    }
                                }
                            }
                        }
                        break;
                }
                Completed = true;
            }
            catch (EndOfStreamException)
            {
                Debug.WriteLine("Tried to read past end of Bitstream!!!");
                Debug.Unindent();
            }

        EndOfBitstream:
            Tile.Layer = l;
            Tile.Resolution = r;
            Tile.Component = c;
            Tile.Precinct = p;

            if (Source.Position == endPosition) Debug.WriteLine("End of Bitstream");
            else if (Source.Position < endPosition)
            {
                Debug.WriteLine("Failed to read all of bitstream. " + (endPosition - Source.Position) + " bytes remaining.");
                while (Source.Position < endPosition) Source.ReadByte();
            }
            else Debug.WriteLine("Read " + (Source.Position - endPosition) + " bytes past end of Bitstream!!!");

            Debug.Unindent();
            return Completed;
        }

        public void ReadPacket(BitstreamReader br, int l, int r, int c, int p)
        {
            Debug.WriteLine("Packet " + Packets.Count + ": " + br.Source.Position + ": Layer " + l + " Resolution " + r + " Component " + c + " Precinct " + p);
            Debug.Indent();

            Packet packet = new Packet(this, br, l, r, c, p);
            Packets.Add(packet);

            Tile.PacketCompleted[l, r, c, p] = true;

            Debug.Unindent();
        }
    }
}
